Proof-of-concept destructive future combinators#23
Closed
grddev wants to merge 1 commit into
Closed
Conversation
Implement destructive versions of the combinators, that updates the existing future rather than creating a new one. The new combinators ought to be thread-safe, but interleaving destructive updates might produce counter-intuitive results sometimes. However, the common use case seems to be synchronous construction of an asynchronous future chain, in which case these combinators might reduce memory footprint and call stack depth. This is made possible by not resolving a future until after the callbacks have completed. This is obviously a backwards-incompatible change, as it causes any code with a `#value` in a callback to hang indefinitely. An obvious alternative would be to treat "transformations" and "callbacks" separately, but I tried to avoid introducing further bookkeeping surrounding the callbacks. Another advantage of keeping callbacks and transformations in a list is that synchronous transformations behave as expected. That is, if you add a listener before and after a `map!` is applied, the first callback will receive the unmapped value, and the second callback will receive the transformed value. The future chaining transformations (`flat_map`, `then` and `fallback`) requires some way to stop the listener dispatch in order to implement the destructive versions. Here, this is accomplished by using the return value of the listener callback. There is probably a better way to get the same effect. To avoid breaking existing callbacks, this was separated into the `on_complete!` callback. To avoid too much code duplication, the non-destructive operation `foo` is implemented using `dup.foo!`, similar to how non-destructive array operations can be implemented. Not quite sure `dup` is the right name for the newly constructed future (observing on the original), though. I made some minor adjustments to the remaining code to get the future specs to pass, but these are probably not 100% correct. I have not (yet) made any performance comparisons with the existing implementations. These changes are obviously not worth the trouble unless the destructive operations are significantly cheaper than the previous implementation of their non-destructive counterparts. For now, the destructive methods are only tested through the existing tests for the non-destructive versions. Further tests and documentation would definitely be necessary to better describe the semantics.
Collaborator
Author
|
Forgot to mention: the current implementation does not really work well when the future is resolved before the call to a combinator. The reason for this is that I wanted to maintain the behaviour that a future goes from pending to resolved exactly once. That is, if My idea for this was that the destructive combinators would in fact only be destructive until the future was completed, and after that point return new futures. While the contract for the destructive combinators would be quite strange, this would fit rather well with some of the optimizations in #17. I never implemented that though, which is the reason why the tests fail on travis. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implement destructive versions of the combinators, that updates the
existing future rather than creating a new one. The new combinators
ought to be thread-safe, but interleaving destructive updates might
produce counter-intuitive results sometimes. However, the common use
case seems to be synchronous construction of an asynchronous future
chain, in which case these combinators might reduce memory footprint
and call stack depth.
This is made possible by not resolving a future until after the
callbacks have completed. This is obviously a backwards-incompatible
change, as it causes any code with a
#valuein a callback to hangindefinitely.
An obvious alternative would be to treat "transformations" and
"callbacks" separately, but I tried to avoid introducing further
bookkeeping surrounding the callbacks.
Another advantage of keeping callbacks and transformations in a list
is that synchronous transformations behave as expected. That is, if
you add a listener before and after a
map!is applied, the firstcallback will receive the unmapped value, and the second callback will
receive the transformed value.
The future chaining transformations (
flat_map,thenandfallback)requires some way to stop the listener dispatch in order to implement
the destructive versions. Here, this is accomplished by using the
return value of the listener callback. There is probably a better way
to get the same effect. To avoid breaking existing callbacks, this was
separated into the
on_complete!callback.To avoid too much code duplication, the non-destructive operation
foois implemented using
dup.foo!, similar to how non-destructive arrayoperations can be implemented. Not quite sure
dupis the right namefor the newly constructed future (observing on the original), though.
I made some minor adjustments to the remaining code to get the future
specs to pass, but these are probably not 100% correct.
I have not (yet) made any performance comparisons with the existing
implementations. These changes are obviously not worth the trouble
unless the destructive operations are significantly cheaper than the
previous implementation of their non-destructive counterparts.
For now, the destructive methods are only tested through the existing
tests for the non-destructive versions. Further tests and documentation
would definitely be necessary to better describe the semantics.